{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 132,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "113.41\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import json\n",
    "from pydub import AudioSegment\n",
    "from aubio import source, onset, pitch\n",
    "import random as rd\n",
    "import bpm_get\n",
    "#####################################################################\n",
    "name = \"周深 - 亲爱的旅人啊\"  #音乐名，请保持音乐和谱面名称相同，音乐请使用mp3格式\n",
    "hold_con = \"auto\"  #推荐值:1；越大，生成的hold越多；若赋值为\"auto\"，将根据音乐智能决定该值大小\n",
    "chain_con = \"auto\"  #推荐值:0.1；越大，生成的chain越多；若赋值为\"auto\"，将根据音乐智能决定该值大小\n",
    "bpm = 113.41 #若赋值为\"auto\":将利用内置函数自动测量bpm，否则请在此手动输入bpm\n",
    "#####################################################################\n",
    "song_name = name + \".mp3\"\n",
    "if bpm == \"default\":\n",
    "    bpm = round(bpm_get.get_file_bpm(song_name),2)\n",
    "song = AudioSegment.from_mp3(song_name)\n",
    "song_time = song.duration_seconds\n",
    "tick_total = song_time * bpm * 8\n",
    "print(bpm)   #cylheim初始化谱面时需填写该信息"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 133,
   "metadata": {},
   "outputs": [],
   "source": [
    "win_s = 512                 # fft size\n",
    "hop_s = win_s // 2          # hop size\n",
    "\n",
    "filename = name + \".mp3\"\n",
    "\n",
    "samplerate = 0\n",
    "s = source(filename, samplerate, hop_s)\n",
    "samplerate = s.samplerate\n",
    "\n",
    "o = onset(\"default\", win_s, hop_s, samplerate)\n",
    "\n",
    "    # list of onsets, in samples\n",
    "my_onsets = []\n",
    "\n",
    "# total number of frames read\n",
    "total_frames = 0\n",
    "while True:\n",
    "    samples, read = s()\n",
    "    if o(samples):\n",
    "        my_onsets.append(float(o.get_last_s()))\n",
    "    total_frames += read\n",
    "    if read < hop_s: \n",
    "        break\n",
    "my_onsets.append(float(\"inf\"))\n",
    "my_onsets = [0] + my_onsets\n",
    "    \n",
    "def get_onsets_chains(chain_con, my_onsets):\n",
    "    onsets = []\n",
    "    chains = []\n",
    "    for i in range(1,len(my_onsets)-1):\n",
    "        if my_onsets[i] - my_onsets[i-1] < chain_con or my_onsets[i+1] - my_onsets[i] < chain_con:\n",
    "            chains.append(my_onsets[i])\n",
    "        else:\n",
    "            onsets.append(my_onsets[i])\n",
    "        now = o\n",
    "    return onsets,chains\n",
    "\n",
    "filename = name + \".mp3\"\n",
    "\n",
    "downsample = 1\n",
    "samplerate = 44100 // downsample\n",
    "\n",
    "win_s = 4096 // downsample # fft size\n",
    "hop_s = 512  // downsample # hop size\n",
    "\n",
    "s = source(filename, samplerate, hop_s)\n",
    "samplerate = s.samplerate\n",
    "\n",
    "tolerance = 0.8\n",
    "\n",
    "pitch_o = pitch(\"yin\", win_s, hop_s, samplerate)\n",
    "pitch_o.set_unit(\"midi\")\n",
    "pitch_o.set_tolerance(tolerance)\n",
    "\n",
    "# total number of frames read\n",
    "pitches = []\n",
    "frames = []\n",
    "total_frames = 0\n",
    "while True:\n",
    "    samples, read = s()\n",
    "    my_pitch = pitch_o(samples)[0]\n",
    "    pitches.append(my_pitch)\n",
    "    frames.append(total_frames)\n",
    "    total_frames += read\n",
    "    if read < hop_s:\n",
    "        break\n",
    "\n",
    "def get_starts_ends(hold_con):\n",
    "    starts = []\n",
    "    ends = []\n",
    "    start = 0\n",
    "    end = 0\n",
    "    count = 0\n",
    "    for i in range(1,len(pitches)):\n",
    "        if pitches[i] != 0 and abs(pitches[i] - pitches[i-1]) <= hold_con:\n",
    "            count += 1\n",
    "            end = frames[i] / float(samplerate)\n",
    "        else:\n",
    "            if count >= 30:\n",
    "                starts.append(start)\n",
    "                ends.append(end)\n",
    "            start = frames[i] / float(samplerate)\n",
    "            count = 1    \n",
    "    return starts,ends"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 134,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "click:  467\n",
      "hold:  47\n",
      "chain:  384\n"
     ]
    }
   ],
   "source": [
    "hold_std = 0.06733389961960336\n",
    "chain_std = 0.2198323300217897 * 2\n",
    "if chain_con == \"auto\":\n",
    "    chain_con = 0.1\n",
    "    delta = None\n",
    "    while True:\n",
    "        onsets, chains = get_onsets_chains(chain_con, my_onsets)\n",
    "        ratio = len(chains)/(len(onsets)+len(chains))\n",
    "        if delta == None:\n",
    "            delta = 0.005 if ratio < chain_std else -0.005\n",
    "        elif delta*(ratio-chain_std)>=0:\n",
    "            [onsets,chains] = [onsets,chains] if abs(ratio-chain_std) < abs(pre_ratio-chain_std) else [pre_onsets,pre_chains]\n",
    "            break\n",
    "        pre_onsets = onsets\n",
    "        pre_chains = chains\n",
    "        pre_ratio = ratio\n",
    "        chain_con += delta\n",
    "else:\n",
    "    onsets, chains = get_onsets_chains(chain_con)\n",
    "    \n",
    "\n",
    "if hold_con == \"auto\":\n",
    "    hold_con = 1\n",
    "    delta = None\n",
    "    while True:\n",
    "        starts, ends = get_starts_ends(hold_con)\n",
    "        ratio = len(starts)/(len(onsets)+len(chains))\n",
    "        if delta == None:\n",
    "            delta = 0.05 if ratio < hold_std else -0.05\n",
    "        elif delta*(ratio-hold_std)>=0:\n",
    "            [starts,ends] = [starts,ends] if abs(ratio-hold_std) < abs(pre_ratio-hold_std) else [pre_starts,pre_ends]\n",
    "            break\n",
    "        pre_starts = starts\n",
    "        pre_ends = ends\n",
    "        pre_ratio = ratio\n",
    "        hold_con += delta\n",
    "else:\n",
    "    starts, ends = get_starts_ends(hold_con)\n",
    "\n",
    "i,j=0,0\n",
    "while True:\n",
    "    try:\n",
    "        if starts[j] < onsets[i]:\n",
    "            j += 1\n",
    "        else:\n",
    "            if starts[j] - onsets[i] <= 0.2:\n",
    "                onsets.pop(i)\n",
    "            else:\n",
    "                i += 1\n",
    "    except IndexError:\n",
    "        break\n",
    "        \n",
    "print(\"click: \",len(onsets))\n",
    "print(\"hold: \",len(starts))\n",
    "print(\"chain: \",len(chains))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 135,
   "metadata": {},
   "outputs": [],
   "source": [
    "pre_x = float(\"inf\")\n",
    "def produce_x(p):\n",
    "    x = rd.random()\n",
    "    if p % 2 == 0:\n",
    "        while not (0.1<=x<=0.2 or 0.55<=x<=0.7):\n",
    "            x = rd.random()\n",
    "    else:\n",
    "        while not (0.3<=x<=0.45 or 0.8<=x<=0.9):\n",
    "            x = rd.random()  \n",
    "    return x\n",
    "    \n",
    "\n",
    "def produce_n(t, c, my_id, hold_tick=0):\n",
    "    tick = round(c*tick_total/song_time)\n",
    "    page_index = int(tick / 960)\n",
    "    n = {'hold_tick': hold_tick*tick_total/song_time, \n",
    "             'type': t, \n",
    "             'next_id': -1, \n",
    "             'x': produce_x(page_index), \n",
    "             'tick': tick, \n",
    "             'page_index': page_index, \n",
    "             'id': my_id, \n",
    "             'has_sibling': False, \n",
    "             'is_forward': False}\n",
    "    return n\n",
    "\n",
    "def produce_0(n):\n",
    "    return n\n",
    "\n",
    "def produce_1(n):\n",
    "    global pre_x\n",
    "    if n['page_index'] != int((n['tick'] + n['hold_tick']) / 960):\n",
    "        n['type'] = 2\n",
    "    p = n[\"page_index\"]\n",
    "    while abs(n['x'] - pre_x) < 0.1:\n",
    "        n['x'] = produce_x(p)\n",
    "    pre_x = n['x']\n",
    "    return n  \n",
    "\n",
    "def produce_3(n):\n",
    "    return n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 136,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[7.058866213151927, 7.918004535147392, 16.69514739229025, 17.69360544217687, 20.36390022675737, 29.559002267573696, 34.40036281179138, 39.021133786848075, 39.566802721088436, 46.13804988662132, 52.024308390022675, 53.6497052154195, 56.32, 56.81922902494331, 58.95546485260771, 64.89977324263039, 66.38585034013606, 66.94312925170068, 71.3317006802721, 72.3998185941043, 84.14911564625851, 85.27528344671202, 93.97115646258503, 100.76299319727892, 101.30866213151927, 105.08190476190477, 110.67791383219955, 111.49061224489796, 113.49913832199546, 126.01469387755103, 135.65097505668933, 139.91183673469388, 151.78884353741498, 152.78730158730158, 153.51873015873016, 163.00408163265305, 164.2463492063492, 165.05904761904762, 174.97396825396825, 184.33160997732426, 214.5407709750567, 216.46802721088434, 217.02530612244897, 222.4471655328798, 226.64997732426303, 227.14920634920634, 233.66240362811791]\n"
     ]
    }
   ],
   "source": [
    "print(starts)\n",
    "note_name = name + \".json\"\n",
    "note = json.loads(open(note_name,encoding = \"utf-8\").read())\n",
    "note_list = []\n",
    "count = 0\n",
    "while starts != [] or onsets != [] or chains != []:\n",
    "    try:\n",
    "        start = starts[0]\n",
    "    except IndexError:\n",
    "        start = float(\"inf\")\n",
    "    try:\n",
    "        onset = onsets[0]\n",
    "    except IndexError:\n",
    "        onset = float(\"inf\")\n",
    "    try:\n",
    "        chain = chains[0]\n",
    "    except IndexError:\n",
    "        chain = float(\"inf\")\n",
    "    min_v = min(start,onset,chain)\n",
    "    if onset == min_v:\n",
    "        onsets.pop(0)\n",
    "        n = produce_n(0,onset,count)\n",
    "        if n['page_index'] == 0:\n",
    "            continue\n",
    "        n = produce_0(n)\n",
    "    elif start == min_v:\n",
    "        starts.pop(0)\n",
    "        n = produce_n(1,start,count,hold_tick = ends.pop(0) - start)\n",
    "        if n['page_index'] == 0:\n",
    "            continue\n",
    "        n = produce_1(n)\n",
    "    else:\n",
    "        chains.pop(0)\n",
    "        n = produce_n(3,chain,count)\n",
    "        if n['page_index'] == 0:\n",
    "            continue  \n",
    "        n = produce_3(n)\n",
    "        if count!= 0 and (note_list[-1]['type'] == 3 or note_list[-1]['type'] == 4) and n['tick'] - note_list[-1]['tick'] < 0.5*tick_total/song_time:\n",
    "            note_list[-1]['next_id'] = count\n",
    "            n['type'] = 4\n",
    "    note_list.append(n)\n",
    "    count += 1\n",
    "\n",
    "\n",
    "if note_list[0][\"type\"] == 3 and note_list[1][\"type\"] != 4:\n",
    "    note_list[0][\"type\"] = 0\n",
    "for i in range(1,len(note_list)-1):\n",
    "    if note_list[i][\"type\"] == 3 and note_list[i+1][\"type\"] != 4:\n",
    "        j = 1\n",
    "        while note_list[i+j][\"type\"] == 1 or note_list[i+j][\"type\"] == 2:\n",
    "            j += 1\n",
    "        note_list[i+j][\"type\"] = 4\n",
    "        note_list[i][\"next_id\"] = note_list[i+j][\"id\"]  \n",
    "end = None\n",
    "now_x = None\n",
    "i = 0\n",
    "while i < len(note_list):\n",
    "    if 1<=note_list[i][\"type\"]<=2:\n",
    "        end = note_list[i][\"tick\"] + note_list[i][\"hold_tick\"]\n",
    "        now_x = note_list[i][\"x\"]\n",
    "        if note_list[i-1][\"type\"] == 3:\n",
    "            while (note_list[i-1]['x']<0.5)==(note_list[i]['x']<0.5):\n",
    "                p = note_list[i-1]['page_index']\n",
    "                note_list[i-1]['x'] = produce_x(p)\n",
    "    else:\n",
    "        if end == None:\n",
    "            i += 1\n",
    "            continue\n",
    "        while (note_list[i]['x'] < 0.5) == (now_x < 0.5):\n",
    "            p = note_list[i]['page_index']\n",
    "            note_list[i]['x'] = produce_x(p)\n",
    "        if note_list[i][\"tick\"] >= end:\n",
    "            end = None  \n",
    "            i -= 1\n",
    "    i += 1\n",
    "for i in range(1,len(note_list)):\n",
    "    if note_list[i][\"next_id\"] != -1:\n",
    "        next_id = note_list[i][\"next_id\"]\n",
    "        if note_list[next_id][\"page_index\"] != note_list[i][\"page_index\"]:\n",
    "            try:\n",
    "                tag = (note_list[i][\"x\"] - note_list[i-1][\"x\"])/abs(note_list[i][\"x\"] - note_list[i-1][\"x\"])\n",
    "            except ZeroDivisionError:\n",
    "                tag = 1\n",
    "            note_list[next_id][\"x\"] = abs(max(0.05,min((rd.random() / 10 + 0.05) *  tag+ note_list[i][\"x\"], 0.95)))\n",
    "        else:\n",
    "            note_list[next_id][\"x\"] = max(0.05,min((rd.random() - 0.5) / 5 + note_list[i][\"x\"], 0.95))\n",
    "note['note_list'] = note_list\n",
    "json.dump(note,open(note_name,\"w\",encoding = \"utf-8\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 112,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.13000000000000003"
      ]
     },
     "execution_count": 112,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain_con"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 113,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.1499999999999997"
      ]
     },
     "execution_count": 113,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "hold_con"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 114,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.053469852104664393"
      ]
     },
     "execution_count": 114,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ratio"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 121,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 121,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(starts)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
